package com.halachawiki.server.login;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Random;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.halachawiki.client.login.LoginService;
import com.halachawiki.server.PermissionManager;
import com.halachawiki.server.Settings;
import com.halachawiki.server.data.User;
import com.halachawiki.server.data.UserContainer;
import com.halachawiki.shared.FieldVerifier;
import com.halachawiki.shared.PermissionException;
import com.halachawiki.shared.PublicUserInfo;

@SuppressWarnings("serial")
public class LoginServiceImp extends RemoteServiceServlet implements LoginService {
	private static Logger logger = Logger.getLogger(LoginServiceImp.class.getName());

	@Override
	public Status userExists(String email) throws IllegalArgumentException {
		User user = UserContainer.getUserByEmail(email);
		if (user == null)
			return Status.NO_SUCH_USER;
		return Status.OK; // A user exists even if not confirmed or approved.
	}

	@Override
	public Status registerNewUser(String email, String password,
			String displayName, String aboutMe, String locale) {
		User user = new User(email, password, displayName, aboutMe, locale);
		if (!UserContainer.addUser(user)) {
			return Status.GENERIC_ERROR;
		}
		if (Settings.usersNeedConfirmation) {
			sendConfirmationEmail(user);
		}

		// should not fail
		return logInUser(email, password, false);
	}


	@Override
	public boolean resendConfirmationEmail() {
		User user = UserContainer.getLogedInUser(getThreadLocalRequest());
		if (user == null) { return false; }
		return sendConfirmationEmail(user);
	}
	
	
	
	@Override
	public Status logInUser(String email, String password, boolean rememberMe) {
		User user = UserContainer.getUserByEmail(email);
		if (user == null)
			return Status.NO_SUCH_USER;
		if (!user.getPassword().equals(password))
			return Status.WRONG_PASSWORD;
		UserContainer.setLoggedInUser(user, rememberMe, getThreadLocalRequest());
		return Status.OK;
	}

	@Override
	public void logout() {
		UserContainer.setLoggedInUser(null, true, getThreadLocalRequest());
	}
	
	@Override
	public Status sendPasswordReminder(String email) {
		User user = UserContainer.getUserByEmail(email);
		if (user == null)
			return Status.NO_SUCH_USER;
		
		Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        String subject = getText(user.getLocale(),"passwordReminderSubject");
        String msgBody = getTextWithParameters(user.getLocale(), "passwordReminderPattern", new Object[]{user.getPassword()} );

        try {
            MimeMessage msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress(Settings.adminEmail, "HalachaWiki.com Admin"));
            msg.addRecipient(Message.RecipientType.TO,
                             new InternetAddress(user.getEmail(), user.getDisplayName()));
            msg.setSubject(subject,"UTF-8");
            String html = "<html><body><p>"+ msgBody+"</p></body></html>";
            msg.setContent(html, "text/html; charset=UTF-8");
            Transport.send(msg);
            logger.log(Level.INFO, "Sent password reminder for " + email);
            return Status.OK;
    
        } catch (Exception e) {
            logger.log(Level.SEVERE, "sendPasswordReminder", e);
            return Status.GENERIC_ERROR;
        }
	}
	
	//// Private implementation
	private String generateActivationKey(User user) {
		if (user.getConfirmKey() != null && !user.getConfirmKey().isEmpty())
			return user.getConfirmKey();
		MessageDigest md5;
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return "kuku";
		}
		Random random = new Random();
		int length = 50 + (random.nextInt() % 50);
		byte[] bytes = new byte[length];
		random.nextBytes(bytes);
		byte[] hash = md5.digest(bytes);
		String [] hexLookup = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};
		String result = "";
		for (int i = 0; i < hash.length; i++) {
			int high = (hash[i] >> 4) & 0xf;
			int low = (hash[i] & 0xf);
			result += hexLookup[high] + hexLookup[low];
		}
		user.setConfirmKey(result);
		UserContainer.userChanged(user);
		return result;
	}
	
	
	private boolean sendConfirmationEmail(User user) {
		Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        String activationKey = generateActivationKey(user);
        String activateUrl = Settings.loginUrl;
        if (!activateUrl.contains("?")) { activateUrl += "?"; }
        activateUrl += "&confirm="+activationKey;
      
        String subject = getText(user.getLocale(),"registrationMailSubject");;
        String msgBody = getTextWithParameters(user.getLocale(), "registrationMailPattern", new Object[]{activateUrl});

        try {
            MimeMessage msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress(Settings.adminEmail, "HalachaWiki.com Admin"));
            msg.addRecipient(Message.RecipientType.TO,
                             new InternetAddress(user.getEmail(), user.getDisplayName()));
            msg.setSubject(subject,"UTF-8");
            String html = "<html><body><p>"+ msgBody+"</p></body></html>";
            msg.setContent(html, "text/html; charset=UTF-8");
            
            Transport.send(msg);
    
        } catch (Exception e) {
            logger.log(Level.SEVERE, "sendConfirmationEmail", e);
            return false;
        }
        return true;
	}
	
	@Override
	public Status confirmMe(String confirmationKey) {
		//String confirmationKey = getThreadLocalRequest().getParameter("confirm");
		if (confirmationKey==null || confirmationKey.isEmpty()) {
			return Status.GENERIC_ERROR;
		}
		User user = UserContainer.getLogedInUser(getThreadLocalRequest());
		if (user == null) {
			return Status.NOT_LOGGED_IN;
		}
		if (!user.getConfirmKey().equals(confirmationKey)) {
			return Status.GENERIC_ERROR;
		}
		user.confirm();
		UserContainer.userChanged(user);
		sendApprovalReminder();
		return Status.OK;
	}

	@Override
	public PublicUserInfo getCurrentUserInfo() {
		PublicUserInfo info = new PublicUserInfo();
		User user = UserContainer.getLogedInUser(getThreadLocalRequest());
		if (user == null) {
			info.status = Status.NOT_LOGGED_IN;
			return info;
		}
		info.displayName = user.getDisplayName();
		info.email = user.getEmail();
		info.status = getUserStatus( user);
		return info;
	}

	private Status getUserStatus(User user) {
		if (user.getBanned())
			return Status.USER_BANNED;
		if (Settings.usersNeedConfirmation && !user.isConfirmed())
			return Status.USER_NOT_CONFIRMED;
		if (Settings.usersNeedAdminApproval && !user.isApproved())
			return Status.USER_NOT_APPROVED;
		return Status.OK;
	}

	@Override
	public Status changePassword(String oldPassword, String newPassword) {
		User user = UserContainer.getLogedInUser(getThreadLocalRequest());
		if (user == null) {
			return Status.NOT_LOGGED_IN;
		}
		if (!user.getPassword().equals(oldPassword)) {
			return Status.WRONG_PASSWORD;
		}
		if (!FieldVerifier.isValidPassword(newPassword)) {
			return Status.ERROR_IVALID_PASSWORD;
		}
		user.setPassword(newPassword);
		UserContainer.userChanged(user);
		return Status.OK;
	}

	@Override
	public Status sendApprovalReminder() {
		User user = UserContainer.getLogedInUser(getThreadLocalRequest());
		if (user == null) {
			return Status.NOT_LOGGED_IN;
		}
		
		Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);
        
        String subject = getText(user.getLocale(), "approvalReminderSubject");
        String msgBody = getTextWithParameters(user.getLocale(), "approvalReminderPattern", new Object[]{user.getEmail()} );

        try {
            MimeMessage msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress(Settings.adminEmail, "HalachaWiki.com Admin"));
            msg.addRecipient(Message.RecipientType.TO,
                             new InternetAddress(Settings.adminEmail, "HalachaWiki.com Admin"));
            msg.setSubject(subject, "UTF-8");
            String html = "<html><body><p>"+ msgBody+"</p></body></html>";
            msg.setContent(html, "text/html; charset=UTF-8");
            Transport.send(msg);
    
        } catch (Exception e) {
            logger.log(Level.SEVERE, "sendApprovalReminder", e);
            return Status.GENERIC_ERROR;
        }
		return Status.OK;
	}

	@Override
	public PublicUserInfo[] getAllUserInfo() throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		List<PublicUserInfo> result = new ArrayList<PublicUserInfo>();
		List<User> users = UserContainer.getAllUsers();
		for (User user : users) {
			PublicUserInfo info = new PublicUserInfo();
			info.displayName = user.getDisplayName();
			info.email = user.getEmail();
			info.status = getUserStatus(user);
			result.add(info);
		}
		return result.toArray(new PublicUserInfo[0]);
	}

	@Override
	public Status approveUser(String email) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		User user = UserContainer.getUserByEmail(email);
		if (user == null) {
			return Status.NO_SUCH_USER;
		}
		user.approve();
		UserContainer.userChanged(user);
		return Status.OK;
	}

	@Override
	public Status changeUserEmail(String oldEmail, String newEmail) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		if (UserContainer.getUserByEmail(newEmail) != null) {
			return Status.GENERIC_ERROR;
		}
		User user = UserContainer.getUserByEmail(oldEmail);
		if (user == null) {
			return Status.NO_SUCH_USER;
		}
		user.setEmail(newEmail);
		UserContainer.userChanged(user);
		return Status.OK;
	}

	@Override
	public PublicUserInfo getUserInfo(String email) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		User user = UserContainer.getUserByEmail(email);
		if (user == null) {
			return null;
		}
		PublicUserInfo info = new PublicUserInfo();
		info.displayName = user.getDisplayName();
		info.email = user.getEmail();
		info.status = getUserStatus(user);
		return info;
	}

	@Override
	public Status banUser(String email) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		User user = UserContainer.getUserByEmail(email);
		if (user == null) {
			return Status.NO_SUCH_USER;
		}
		user.setBanned(true);
		UserContainer.userChanged(user);
		return Status.OK;
	}

	@Override
	public Status unbanUser(String email) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		User user = UserContainer.getUserByEmail(email);
		if (user == null) {
			return Status.NO_SUCH_USER;
		}
		user.setBanned(false);
		UserContainer.userChanged(user);
		return Status.OK;
	}

	@Override
	public Status confirmUserEmail(String email) throws PermissionException {
		PermissionManager.checkUserAdmin(getThreadLocalRequest());
		User user = UserContainer.getUserByEmail(email);
		if (user == null) {
			return Status.NO_SUCH_USER;
		}
		user.confirm();
		UserContainer.userChanged(user);
		return Status.OK;
	}
	
	/**
	 * Get resource bundle by user locale.
	 * @param locale user locale
	 * @return suitable resource bundle
	 */
	private ResourceBundle getResourceBundle(Locale locale) {
		return ResourceBundle.getBundle("com.halachawiki.server.login.LoginServiceConstants", locale);
	}
	
	private String getText(String userLocale, String key) {
		Locale locale = Locale.ENGLISH;
		if(userLocale!=null) {
			locale = new Locale(userLocale);
		}
		ResourceBundle bundle = getResourceBundle(locale);
        return bundle.getString(key);
	}

	private String getTextWithParameters(String userLocale, String patternKey, Object[] params) {
		Locale locale = Locale.ENGLISH;
		if(userLocale!=null) {
			locale = new Locale(userLocale);
		}
		MessageFormat messageFormat = new MessageFormat("");
        messageFormat.setLocale(locale);
        
        ResourceBundle bundle = getResourceBundle(locale);
        String pattern = bundle.getString(patternKey);
        messageFormat.applyPattern(pattern);
        
        return messageFormat.format(params);
	}
	
}
